#ifndef TC_STATS_COLLECTOR_HPP
#define TC_STATS_COLLECTOR_HPP

#if defined(WIRELESS_EXTENSIONS)
#include <iwlib.h> // wireless extensions API
#endif

#include <string>
#include <set>
#include <vector>

using std::string;
using std::set;
using std::vector;


///////////
//----------- FROM iwspy.c -------------
// Set list of addresses specified on the command line in the driver

#if defined(WIRELESS_EXTENSIONS)
static int
set_spy_info(int		skfd,		// The socket
	     char *		ifname,		// Dev name
	     char *		args[],		// Command line args
	     int		count)		// Args count
{
  struct iwreq		wrq;
  int			i;
  int			nbr;		// Number of valid addresses
  struct sockaddr	hw_address[IW_MAX_SPY];

  // Read command line
  i = 0;	// first arg to read
  nbr = 0;	// Number of args read so far

  // "off" : disable functionality (set 0 addresses)
  if(!strcmp(args[0], "off"))
    i = 1;	// skip the "off"
  else
    {
      // "+" : add all addresses already in the driver
      if(!strcmp(args[0], "+"))
	{
	  char	buffer[(sizeof(struct iw_quality) +
			sizeof(struct sockaddr)) * IW_MAX_SPY];

	  // Check if we have valid mac address type
	  if(iw_check_mac_addr_type(skfd, ifname) < 0)
	    {
	      fprintf(stderr,
		      "%-8.16s  Interface doesn't support MAC addresses\n",
		      ifname);
	      return(-1);
	    }

	  wrq.u.data.pointer = (caddr_t) buffer;
	  wrq.u.data.length = IW_MAX_SPY;
	  wrq.u.data.flags = 0;
	  if(iw_get_ext(skfd, ifname, SIOCGIWSPY, &wrq) < 0)
	    {
	      fprintf(stderr, "Interface doesn't accept reading addresses...\n");
	      fprintf(stderr, "SIOCGIWSPY: %s\n", strerror(errno));
	      return(-1);
	    }

	  // Copy old addresses
	  nbr = wrq.u.data.length;
	  memcpy(hw_address, buffer, nbr * sizeof(struct sockaddr));

	  i = 1;	// skip the "+"
	}

      // Read other args on command line
      while((i < count) && (nbr < IW_MAX_SPY))
	{
	  // Get the address and check if the interface supports it
	  if(iw_in_addr(skfd, ifname, args[i++], &(hw_address[nbr])) < 0)
	    continue;
	  nbr++;
	}

      // Check the number of addresses
      if(nbr == 0)
	{
	  fprintf(stderr, "No valid addresses found : exiting...\n");
	  return(-1);
	}
    }

  // Check if there is some remaining arguments
  if(i < count)
    {
      fprintf(stderr, "Got only the first %d arguments, remaining discarded\n", i);
    }

  // Time to do send addresses to the driver
  wrq.u.data.pointer = (caddr_t) hw_address;
  wrq.u.data.length = nbr;
  wrq.u.data.flags = 0;
  if(iw_set_ext(skfd, ifname, SIOCSIWSPY, &wrq) < 0)
    {
      fprintf(stderr, "Interface doesn't accept addresses...\n");
      fprintf(stderr, "SIOCSIWSPY: %s\n", strerror(errno));
      return(-1);
    }

  return(0);
}
#endif // WIRELESS_EXTENSIONS

//////////////////////////////////////////////////////
// TC Statistics Collector class

class TCStatsCollector{
public:
  // Default constructor
  TCStatsCollector() {m_tc_db = NULL;}

  // Constructor with initialization
  TCStatsCollector(int skfd, string ifname, TCDatabaseManager& tc_db)
  {Init(skfd, ifname, tc_db);}

  // Set Database Manager and initialize
  void Init(int skfd, string ifname, TCDatabaseManager& tc_db)
  {m_skfd = skfd; m_ifname = ifname; m_tc_db = &tc_db;}

  // Get link quality for a particular node given its IP address
  // (adds node the driver list if necessary)
  double GetLinkQuality(string ip);

  // Add node to the monitoring list
  void AddNode(string ip)
  {string mac; if (IpToMac(ip, mac)) {m_node_list.insert(ip); UpdateDriverList(ip);}}

  // Remove node from the monitoring list
  void RemoveNode(string ip)
  {string mac; if (IpToMac(ip, mac)) {m_node_list.erase(ip); UpdateDriverList(ip);}}

  // Clear the monitoring list and reset the stats collector
  void Clear() { }

private:
  TCDatabaseManager* m_tc_db; // topology database manager
  std::set<string> m_node_list; // Node list
  int m_skfd; // Generic raw socket descriptor to talk to the kernel
  string m_ifname;

  void UpdateDriverList(string ip);

  double GetQualityFromDriver(string ip);

  bool IpToMac(string& ip, string& mac);
};


//////////
bool TCStatsCollector::IpToMac(string& ip, string& mac)
{
  string IP; bool tpc; int max_power;

  // Check if the node is in the tc_db (get information about the node)
  return m_tc_db->GetNodeInfo(ip, IP, mac, tpc, max_power);
}

//////////
void TCStatsCollector::UpdateDriverList(string ip)
{
  // It is assumed that all nodes in m_node_list are valid in the database manager

  // bufp holds a list of pointers to c-strings containing MAC addresses
  // Note that the driver uses MAC addresses
  char** bufp = (char**) malloc(m_node_list.size() * sizeof(char*));
  int n = 0; // iterator for bufp[ ]
  set<string>::iterator iter; // node_list iterator
  vector<string> mac_list(m_node_list.size()); // list of mac addresses

  // Build the buffer containing the list of MAC addresses to be sent to the driver
  for (iter = m_node_list.begin(); iter != m_node_list.end(); ++iter, ++n){

    // Get MAC address from IP address 
    if ( !IpToMac(const_cast<string&>(*iter), mac_list[n]) ){
      return; // node is not in the database manager (do nothing)
    }

    bufp[n] = const_cast<char*>(mac_list[n].c_str());

#if defined(DEBUG2)
  std::cout << "Adding " << bufp[n] << " to the driver list" << std::endl;
#endif
  }

#if defined(WIRELESS_EXTENSIONS)
  // Send address list to the driver
  if( set_spy_info(m_skfd, const_cast<char*>(m_ifname.c_str()),
                   bufp, m_node_list.size()) < 0 ){
    std::cout << "set_spy_info() failed." << std::endl;
  }
#endif // WIRELESS_EXTENSIONS

  free(bufp);  
}

//////////
double TCStatsCollector::GetQualityFromDriver(string ip)
{
  double link_quality = -1;
  string mac;

  // Get MAC address from IP address 
  if ( !IpToMac(ip, mac) ){
    return link_quality; // node is not in the database manager
  }

#if defined(WIRELESS_EXTENSIONS)

	// Lookup the Rx power level of the packet in the driver's monitor list
	// The following code has been adapted from iwspy.c and iwlib.c
	int i, n;
	struct iwreq wrq;
	struct sockaddr* hwa;
	struct iw_quality* qual;
	char temp[128];
	char buffer[(sizeof(struct iw_quality) +
		     sizeof(struct sockaddr)) * IW_MAX_SPY];
	iwrange range;
	int has_range = 0;

	// Collect stats
	wrq.u.data.pointer = (caddr_t) buffer;
	wrq.u.data.length = IW_MAX_SPY;
	wrq.u.data.flags = 0;

	if ( iw_get_ext(m_skfd, const_cast<char*>(m_ifname.c_str()),
                        SIOCGIWSPY, &wrq) < 0 ){
	  std::cout << "Interface doesn't support wireless statistic collection";
	  std::cout << std::endl;
	  return link_quality;
	}

	// Get range info if we can
	if ( iw_get_range_info(m_skfd, const_cast<char*>(m_ifname.c_str()),
                               &(range)) >= 0 )
	  has_range = 1;

	// Number of addresses
	n = wrq.u.data.length;

	// The two lists: MAC addresses and Link Quality
	hwa = (struct sockaddr *) buffer;
	qual = (struct iw_quality *) (buffer + (sizeof(struct sockaddr) * n));

	for(i = 0; i < n; i++){

	  // if(has_range && (qual[i].level != 0) && (qual[i].level > range.max_qual.level)){

          // MAC address
          iw_pr_ether(temp, (const unsigned char*) &hwa[i].sa_data);

#if defined(DEBUG2)
	  /* Print stats for each address */
          printf("    %s : ", temp);
	  printf("Signal Level: %d dBm\n", qual[i].level - 0x100);
#endif

	  // Check if we have an address match
	  if (mac.compare(0, 17, temp) == 0){
	    link_quality = (double) (qual[i].level - 0x100);
	  }
	  
	  // }
	}

#endif // WIRELESS_EXTENSIONS

  return link_quality;
}

//////////
double TCStatsCollector::GetLinkQuality(string ip)
{
  // It is assumed that all nodes in m_node_list are valid in the database manager

  string MAC_ADDR;
  double link_quality = -1;

  // If node is not in the monitoring list, add it and return
  if (m_node_list.find(ip) == m_node_list.end()){
    AddNode(ip);
    return link_quality; // failed (node is not in the list)
  }

  // If node is already in the list, get link quality
  
  return GetQualityFromDriver(ip);
}

#endif // TC_STATS_COLLECTOR_HPP
